﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[RequireComponent (typeof(NavMeshAgent))]
[RequireComponent (typeof(DectionAI))]
[RequireComponent (typeof(Atributes))]
[RequireComponent (typeof(AudioSource))]

public class GeneralAI : MonoBehaviour
{
		//tree structure accessors
		private node home = new node (false, false);
		//objects for dection code
		private GameObject player;
		private DectionAI myDection;
		private float playerSee;
		private float playerHear;
		public float alertLevel = 40;
		private PlayerControler thePlayerControler;
		//evidence dection
		private GameObject[] evidenceList;
		private int evidenceNo = 0;
		private float evidenceCount = 0;
		//spit dection
		private GameObject[] spitList;
		private int spitNo = 0;
			
		//create tree structure using dictionary refrences for bottem branches (therefore any part of the tree can be accessed)
		//attack tree
		private node nodeAttack;
		private node canSeePlayer;
		//hunt tree
		private node Hunt;
		private node disturbance;
		private node alertToPlayer;
		private node spottedPlayer;
		private node canHearPlayer;

		//movement variables
		private Vector3 startLocation;
		private Vector3 currentTarget;
		public bool walkabout = false;
		public bool walkAboutTurn = true;
		public float walkaboutArea = 10;
		public float speed = 5;
		public float spinDegreesSec = 90;
		//patrol
		private bool done;
		private int waypointNo = 0 ;
		public List<GameObject> waypoints;
		private bool wait;
		private float pointWait;
		private bool activePoint = false;
		private Waypoint currentPoint;
		//nav mesh stuff
		NavMeshAgent navAgent;

		//combat settings
		public float meleeRange = 3.0f;
		public float meleeDamage = 5.0f;
		/// the attack delay in seconds
		public float AttackDelay = 2.0f;
		private float nextAttack;
		private float LOScount;
		///time before AI gives up attack and switches to hunt
		private float LOSDown = 4.0f;

		//hunt variables
		private GameObject evidence;
		private Vector3 evidenceSpot;
		public int huntRange = 35;
		private float turnRoatation = 0;
		private bool check;
		private GameObject alertIcon;

		//sound variables
		public AudioClip AttackSound;
		public AudioClip WalkSound;
		public AudioClip DeathSound;
		public AudioClip HelpSound;
		
		// Use this for initialization
		void Start ()
		{						
				//create tree structure using dictionary refrences for bottem branches (therefore any part of the tree can be accessed)
				//attack tree
				nodeAttack = new node (home, true, false);
				canSeePlayer = new node (nodeAttack, true, false);
				//hunt tree
				Hunt = new node (home, true, false);
				disturbance = new node (Hunt, true, false);
				alertToPlayer = new node (Hunt, true, false);
				spottedPlayer = new node (alertToPlayer, true, false);
				canHearPlayer = new node (alertToPlayer, true, false);

				//conect to nav mesh.
				navAgent = this.GetComponent<NavMeshAgent> ();
				currentTarget = this.transform.position;
				startLocation = this.transform.position;
				//get player
				player = GameObject.FindGameObjectWithTag ("player");
				if (player == null) {
						Debug.LogError ("player missing");
				}
				//get link to detection code
				myDection = (DectionAI)this.GetComponent (typeof(DectionAI));
				thePlayerControler = (PlayerControler)player.GetComponent (typeof(PlayerControler));
				
				nextAttack = Time.time;
				check = true;

				alertIcon = (GameObject)GameObject.Instantiate (GameObject.CreatePrimitive (PrimitiveType.Sphere), new Vector3 (transform.position.x, transform.position.y + 2, transform.position.z), this.transform.rotation);
				alertIcon.transform.parent = this.transform;

				this.tag = "Enemy";
				
		}
	
		// Update is called once per frame
		/*
		* update compares data from the node tree to what is bascily a finite state machine
		* also updates knowalge about the world around the AI
		*/
		void Update ()
		{		
				Debug.DrawLine (this.transform.position, navAgent.pathEndPosition, Color.white);
				int children = home.numberOfChildren ();
				List<int> passedchilds = new List<int> ();
				//{get children of the root}
				for (int i = 0; i < children; i++) {
						node currentChild = home.getChild (i);
						//check for true states
						if (currentChild.hasPassed () == true) {
								//store that child
								passedchilds.Add (i);

						}
				}
				//switch statment to acess diffrent AI states, will only go till a match is found
				if (passedchilds.Count > 0) {
						passedchilds.Sort ();
						switch (passedchilds [0]) {
						case(0):
								{//attack function
										attack ();
										passedchilds.Clear ();
										break;
								}
						case(1):
								{//hunt function
										hunt ();
										passedchilds.Clear ();
										break;
								}
						default:
								{//otherwise do idle (this is a catch statment, should never be used)
										Debug.LogError ("AI switch used defualt catch, state missing");
										idle ();
										passedchilds.Clear ();
										break;
								}
						}//end switch statment
				} else {
						idle ();
				}
				//do sensors checck (to update petrinet)
				// see -- player, glowing spit, dead bodies / eggs / evidence
				playerSee = myDection.EyeDection (player);
				if (playerSee == 0) {
						canSeePlayer.updatePassed (true);
						alertIcon.renderer.enabled = true;
						alertIcon.renderer.material.color = Color.red;
						EvidenceLocation (player, 5);
				} else if (playerSee < alertLevel) {
						spottedPlayer.updatePassed (true);
						EvidenceLocation (player, 5);
				} else {
						canSeePlayer.updatePassed (false);
				}
				//update the players overall dection level
				if (playerSee < 100) {
						thePlayerControler.UpdateAlert (playerSee, this.gameObject);
						alertIcon.renderer.enabled = true;
						alertIcon.renderer.material.color = new Color (0.4f, 0.2F, 0.2f);
				} else {
						alertIcon.renderer.enabled = false;
				}

				//check for evidence
				if (GameObject.FindGameObjectWithTag ("evidence") != null) {
						evidenceList = GameObject.FindGameObjectsWithTag ("evidence");
						//if still going through the list
						if (evidenceNo < evidenceList.Length) {
								float see = myDection.EyeDection (evidenceList [evidenceNo]);
								if (see == 0) {
										evidenceAttach newEvidence = (evidenceAttach)evidenceList [evidenceNo].GetComponent (typeof(evidenceAttach));
										evidenceCount = evidenceCount + newEvidence.retriveEvidence ((GameObject)(this.gameObject));
										evidenceNo ++;
								}
						} else {
								evidenceNo = 0;
						}
				}
				if (evidenceCount >= 5) {
						disturbance.updatePassed (true);
						evidenceCount = 0;
						EvidenceLocation (evidenceList [evidenceNo - 1], 0);
				}
				//check for spit
				if (GameObject.FindGameObjectWithTag ("spit") != null) {
						spitList = GameObject.FindGameObjectsWithTag ("spit");
						//if still going through the list
						if (spitNo < spitList.Length) {
								float see = myDection.EyeDection (spitList [evidenceNo]);
								if (see == 0) {
										currentTarget = spitList [evidenceNo].transform.position;
										walk (currentTarget);
								} else {
										spitNo ++;
								}
						} else {
								spitNo = 0;
						}
				}

				//hear -- player, alert bush, 
				playerHear = myDection.soundDection (player);
				if (playerHear < alertLevel) {
						//may need to add timed function here
						if (!canHearPlayer.hasPassed ()) {
								EvidenceLocation (player, 5);
						}
						canHearPlayer.updatePassed (true);
				}
//				Debug.Log (playerHear);
				//-- for polish --
				//hearing steam mines and investgating
		}

		private void idle ()
		{
				int waypointcount = waypoints.Count;
				if (waypointcount <= 0 && walkabout == false) {
						//if no patrol markers and not on walkabout do nothing
				} else if (waypointcount > 0) {


						//catches starting error
						if (currentTarget != waypoints [waypointNo % waypoints.Count].transform.position) {//this is to catch unwanted areas
								currentTarget = waypoints [waypointNo % waypoints.Count].transform.position;
								check = false;
						}
						//if not at current target
						if (Vector3.Distance (currentTarget, this.transform.position) > 3) {
								//keep moving towards the current target
								walk (currentTarget);
								done = false;
						} else {//otherwise check what to do
								if (done == true && check == true) {
										waypointNo = (waypointNo + 1) % waypointcount;
										currentTarget = waypoints [waypointNo].transform.position;
										turnRoatation = 0;
										//otherwise get data from point
								} else if (check == false) {
										if (waypoints [waypointNo].GetComponent<Waypoint> () != null) {
												currentPoint = waypoints [waypointNo].GetComponent<Waypoint> ();
												pointWait = Time.time + currentPoint.time ();
												activePoint = true;
										} else {
												activePoint = false;
										}
										check = true;
										done = false;
										

								} else if (activePoint == true && done == false) {
										if ((turnRoatation < 360) || (currentPoint.hasRotate () == true)) {

												turnRoatation = turnRoatation + spotRotation (currentPoint.rotation ());
										} else if (currentPoint.hasLook ()) {
												Vector3 diif = currentPoint.lookpoint.transform.position - this.transform.position;
												Vector3 rotateTowards = Vector3.RotateTowards (transform.position, currentPoint.lookpoint.transform.position, 3.0f * Time.deltaTime, 0.0F);
												Quaternion newRotationA = Quaternion.LookRotation (rotateTowards);
												this.transform.rotation = newRotationA;

										} else {
												done = true;	
										}
								}
						}
					
				} else {
						//move randomly around the spawn point
						//pick a random point within x distance from starting point, move to it, then pick a new one
						if (Vector3.Distance (currentTarget, this.transform.position) < 3 && check == true) {
								Vector2 xy = Random.insideUnitCircle;
								Vector3 xyz = new Vector3 (startLocation.x + (xy.x * walkaboutArea), startLocation.y, startLocation.z + (xy.y * walkaboutArea));
								RaycastHit hit;
								Debug.DrawLine (startLocation, xyz, Color.yellow, 2);
								Debug.DrawLine (this.transform.position, xyz, Color.yellow, 2);
								if (Physics.Linecast (this.transform.position, xyz, out hit)) {
										currentTarget = hit.point;
								} else {
										currentTarget = xyz;
								}
								check = false;
								turnRoatation = 0;
								//if at the point turn around on the spot
						} else if (Vector3.Distance (currentTarget, this.transform.position) < 3 && check == false) {
								if (turnRoatation > 360 || walkAboutTurn == false) {
										check = true;
								} else {
										turnRoatation += spotRotation (spinDegreesSec);
								}
						}
						walk (currentTarget);
				}
		}

		
		/*
		 * Hunt makes the AI go into hunt mode,
		 * Two gorillas should not have overlapping search areas
		 */
		private void hunt ()
		{
				navAgent.SetDestination (evidenceSpot);
				if (check == false && Vector3.Distance (navAgent.pathEndPosition, this.transform.position) < 3) {
						//turn around
						if (turnRoatation < 360.0f) {
								turnRoatation += spotRotation (spinDegreesSec);
						} else {
								check = true;
						}		
//						//find cover and hidyHoles
				} else if (check == false) {
						//search towards that location
						walk (evidenceSpot);
						turnRoatation = 0;
						turnRoatation = 0;
//						Debug.Log ("still moving towards destination");
						Debug.DrawLine (this.transform.position, navAgent.pathEndPosition, Color.green);
						
				} else if (check == true) {
						//&& objectcheck == false && checkcountCover >= cover.Length && checkcountHidyHole >= hidyHoles.Length
//						Debug.Log ("ending hunt");
						spottedPlayer.updatePassed (false);
						canHearPlayer.updatePassed (false);
						disturbance.updatePassed (false);
				}
			
		}


		/*
		 * have the AI attack the player
		 */
		private void attack ()
		{
				Debug.DrawLine (this.transform.position, player.transform.position, Color.red);
				//close distance as best as possible
				if (Vector3.Distance (player.transform.position, this.transform.position) > meleeRange - 1) {
						//keep moving towards the current target
						walk (player.transform.position);
				} else if (nextAttack < Time.time) {//if close enough attack the target
						meleeAttack.rayAttack (this.gameObject, meleeRange, player.tag, (int)meleeDamage);
						nextAttack = Time.time + AttackDelay;
						playSound (AttackSound);
				}
				//after 10 seconds of attacking alert nearby gorillas -- todo--
				
				//if los is broken for more then a few seconds go into huntting mode
				if (playerSee > 0) {
//						Debug.Log ("lost los");
						if (LOScount > Time.time) {
								canSeePlayer.updatePassed (false);
								spottedPlayer.updatePassed (true);
								EvidenceLocation (player, 0);
						}
				} else {
						LOScount = Time.time + LOSDown;
				}
//				Debug.Log ("Attack");
		}

		/**
		 * Gets the location of a bit of evidence and assigns it to the location point
		 * fuzzy will blur the location ( a simulation guess), where 0 is exact
		 */
		private void EvidenceLocation (GameObject item, float fuzzy)
		{
				evidence = item;
				Vector2 xy = Random.insideUnitCircle;
				Vector3 xyz = new Vector3 (item.transform.position.x + (xy.x * fuzzy), item.transform.position.y, item.transform.position.z + (xy.y * fuzzy));
				evidenceSpot = xyz;
				check = false;
		}

		/**
		 * function to make the AI rotate around on the spot
		 */
		private float spotRotation (float angle)
		{
				float angleDelta = angle * Time.deltaTime;
				this.transform.RotateAround (this.transform.position, Vector3.up, angleDelta);
				return angleDelta;
		}

		/**
		 * returns the Alert level of a AI
		 */
		public float awareness ()
		{
				return playerSee;
		}

		/*
		 * walk function, to add sounds
		 */
		private void walk (Vector3 desination)
		{
				navAgent.SetDestination (desination);
				playSound (WalkSound);
		}
		public void playSound (AudioClip sound)
		{
				if (audio.isPlaying == false) {
						audio.clip = sound;
						audio.Play ();
				}

		}
}
